1   /*
2    * Copyright (C) 2011 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.util.concurrent;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static java.util.concurrent.TimeUnit.MILLISECONDS;
21  import static java.util.concurrent.TimeUnit.NANOSECONDS;
22  import static junit.framework.Assert.fail;
23  
24  import com.google.common.testing.TearDown;
25  import com.google.common.testing.TearDownAccepter;
26  
27  import java.util.concurrent.TimeUnit;
28  import java.util.logging.Logger;
29  
30  /**
31   * Utilities for performing thread interruption in tests
32   * 
33   * @author Kevin Bourrillion
34   * @author Chris Povirk
35   */
36  final class InterruptionUtil {
37    private static final Logger logger =
38        Logger.getLogger(InterruptionUtil.class.getName());
39  
40    /**
41     * Runnable which will interrupt the target thread repeatedly when run.
42     */
43    private static final class Interruptenator implements Runnable {
44      private final long everyMillis;
45      private final Thread interruptee;
46      private volatile boolean shouldStop = false;
47  
48      Interruptenator(Thread interruptee, long everyMillis) {
49        this.everyMillis = everyMillis;
50        this.interruptee = interruptee;
51      }
52  
53      @Override
54      public void run() {
55        while (true) {
56          try {
57            Thread.sleep(everyMillis);
58          } catch (InterruptedException e) {
59            // ok. just stop sleeping.
60          }
61          if (shouldStop) {
62            break;
63          }
64          interruptee.interrupt();
65        }
66      }
67  
68      void stopInterrupting() {
69        shouldStop = true;
70      }
71    }
72  
73    /**
74     * Interrupts the current thread after sleeping for the specified delay.
75     */
76    static void requestInterruptIn(final long time, final TimeUnit unit) {
77      checkNotNull(unit);
78      final Thread interruptee = Thread.currentThread();
79      new Thread(new Runnable() {
80        @Override
81        public void run() {
82          try {
83            unit.sleep(time);
84          } catch (InterruptedException wontHappen) {
85            throw new AssertionError(wontHappen);
86          }
87          interruptee.interrupt();
88        }
89      }).start();
90    }
91  
92    static void repeatedlyInterruptTestThread(
93        long interruptPeriodMillis, TearDownAccepter tearDownAccepter) {
94      final Interruptenator interruptingTask =
95          new Interruptenator(Thread.currentThread(), interruptPeriodMillis);
96      final Thread interruptingThread = new Thread(interruptingTask);
97      interruptingThread.start();
98      tearDownAccepter.addTearDown(new TearDown() {
99        @Override public void tearDown() throws Exception {
100         interruptingTask.stopInterrupting();
101         interruptingThread.interrupt();
102         joinUninterruptibly(interruptingThread, 2500, MILLISECONDS);
103         Thread.interrupted();
104         if (interruptingThread.isAlive()) {
105           // This will be hidden by test-output redirection:
106           logger.severe(
107               "InterruptenatorTask did not exit; future tests may be affected");
108           /*
109            * This won't do any good under JUnit 3, but I'll leave it around in
110            * case we ever switch to JUnit 4:
111            */
112           fail();
113         }
114       }
115     });
116   }
117 
118   // TODO(cpovirk): promote to Uninterruptibles, and add untimed version
119   private static void joinUninterruptibly(
120       Thread thread, long timeout, TimeUnit unit) {
121     boolean interrupted = false;
122     try {
123       long remainingNanos = unit.toNanos(timeout);
124       long end = System.nanoTime() + remainingNanos;
125 
126       while (true) {
127         try {
128           // TimeUnit.timedJoin() treats negative timeouts just like zero.
129           NANOSECONDS.timedJoin(thread, remainingNanos);
130           return;
131         } catch (InterruptedException e) {
132           interrupted = true;
133           remainingNanos = end - System.nanoTime();
134         }
135       }
136     } finally {
137       if (interrupted) {
138         Thread.currentThread().interrupt();
139       }
140     }
141   }
142 }